/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ru.dusty.lab2.write;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import ru.dusty.lab2.raid.utils.RaidConfig;
import ru.dusty.lab2.raid.utils.Utils;

/**
 *
 * @author vadim
 */
public class RaidWrite {
    
    private static final String NAME = "write";
    
    private RaidConfig config;
    
    private String root;
    
    private void writeRaid0(int block, byte[] fileBytes) throws FileNotFoundException, IOException {
        
        int bytesPart = fileBytes.length / config.getDisksCount(); 
        int index = 0;
        for (int i = 0; i < config.getDisksCount(); i++) {
            RandomAccessFile raf = new RandomAccessFile(
                    new File(root + File.separator + "disk" + i), "rw");
            raf.seek(block * config.getBlockSize());
            if (i + 1 == config.getDisksCount()) {
                raf.writeShort(fileBytes.length - index);
                raf.write(fileBytes, index, fileBytes.length - index);
            } else {
                raf.writeShort(bytesPart);
                raf.write(fileBytes, index, bytesPart);
                index += bytesPart;
            }
            
            raf.close();
        }
    }
    
    private void writeRaid1(int block, byte[] fileBytes) throws FileNotFoundException, IOException {
        
        int diskNumber = block / config.getBlocksCount();
        long offsetBlock = block % config.getBlocksCount();
        RandomAccessFile raf = new RandomAccessFile(
                new File(root + File.separator + "disk" + diskNumber), "rw");
        raf.seek(offsetBlock * config.getBlockSize());
        raf.writeShort(fileBytes.length);
        raf.write(fileBytes);
        raf.close();
        
        raf = new RandomAccessFile(
                new File(root + File.separator + "disk" + diskNumber + "mirror"), "rw");
        raf.seek(offsetBlock * config.getBlockSize());
        raf.writeShort(fileBytes.length);
        raf.write(fileBytes);
        raf.close();
    }
    
    private List<byte[]> prepareDataForRaid3(byte[] fileBytes) {
        List<byte[]> result = new ArrayList<byte[]>();
        byte[] bytes = new byte[config.getBlockSize()];
        
        for (int i = 0; i < config.getDisksCount() - 1; i++) {
            int index = 0;
            int j = i;
            while (j < fileBytes.length) {
                bytes[index] = fileBytes[j];
                index++;
                j += config.getDisksCount() - 1;
            }
            result.add(Arrays.copyOfRange(bytes, 0, index));
        }
        
        return result;
    }
    
    private void writeParityBlock(int block, int xorLength, 
            List<byte[]> xorData, int parityDisk) throws FileNotFoundException, IOException {
        RandomAccessFile raf = new RandomAccessFile(
                    new File(root + File.separator + "disk" + parityDisk), "rw");
        raf.seek(block * config.getBlockSize());
        raf.writeShort(xorLength);
        
        int maxLength = xorData.get(0).length;
        for (int i = 0; i < maxLength; i++) {
            int result = xorData.get(0)[i];
            for (int j = 1; j < xorData.size(); j++) {
                result ^= xorData.get(j)[i];
            }
            raf.writeByte(result);
        }
        
        raf.close();
    }
    
    private void writeRaid3(int block, byte[] fileBytes) throws FileNotFoundException, IOException {
        
        List<byte[]> bytes = prepareDataForRaid3(fileBytes);
        int maxLength = bytes.get(0).length;
        
        List<byte[]> xorData = new ArrayList<byte[]>();
        int xorLength = 0;
        for (int i = 0; i < config.getDisksCount()- 1; i++) {
            RandomAccessFile raf = new RandomAccessFile(
                    new File(root + File.separator + "disk" + i), "rw");
            raf.seek(block * config.getBlockSize());
            raf.writeShort(bytes.get(i).length);
            if (i == 0) xorLength = bytes.get(i).length;
            else xorLength ^= bytes.get(i).length;
            
            raf.write(bytes.get(i));
            
            if (bytes.get(i).length < maxLength) {
                byte[] temp = Arrays.copyOf(bytes.get(i), maxLength);
                temp[maxLength - 1] = raf.readByte();
                xorData.add(temp);
            } else {
                xorData.add(bytes.get(i));
            }
            
            raf.close();
        }
        
        writeParityBlock(block, xorLength, xorData, config.getDisksCount() - 1);
    }
    
    private void writeRaid4_5(int block, byte[] fileBytes, int parityDisk) throws FileNotFoundException, IOException {
        int bytesPart = fileBytes.length / (config.getDisksCount() - 1);
        int offset = fileBytes.length % (config.getDisksCount() - 1);
        int index = 0;
        List<byte[]> xorData = new ArrayList<byte[]>();
        int xorLength = 0;
        for (int i = 0; i < config.getDisksCount(); i++) {
            if (i == parityDisk) continue;
            
            RandomAccessFile raf = new RandomAccessFile(
                    new File(root + File.separator + "disk" + i), "rw");
            raf.seek(block * config.getBlockSize());
            if (fileBytes.length - index < 2 * bytesPart) {
                if (i == 0) xorLength = fileBytes.length - index;
                else xorLength ^= (fileBytes.length - index);
                
                raf.writeShort(fileBytes.length - index);
                raf.write(fileBytes, index, fileBytes.length - index);
                xorData.add(Arrays.copyOfRange(fileBytes, index, fileBytes.length));
            } else {
                if (i == 0) xorLength = bytesPart;
                else xorLength ^= bytesPart;
                
                raf.writeShort(bytesPart);
                raf.write(fileBytes, index, bytesPart);
                byte[] temp = Arrays.copyOf(Arrays.copyOfRange(fileBytes, index, index + bytesPart), bytesPart + offset);
                for (int j = 0; j < offset; j++) temp[temp.length - j - 1] = raf.readByte();
                xorData.add(temp);
                index += bytesPart;
            }
            
            raf.close();
        }
        
        
        writeParityBlock(block, xorLength, xorData, parityDisk);
    }
    
    private void writeRaid4(int block, byte[] fileBytes) throws FileNotFoundException, IOException {
        writeRaid4_5(block, fileBytes, config.getDisksCount() - 1);
    }
    
    private void writeRaid5(int block, byte[] fileBytes) throws FileNotFoundException, IOException {
        int parityDisk = config.getDisksCount() - (block % config.getDisksCount()) - 1;
        writeRaid4_5(block, fileBytes, parityDisk);
    }
    
    private byte[] readImportFile(String filePath) {
        File file = new File(filePath);
        RandomAccessFile rafFile;
        try {
            rafFile = new RandomAccessFile(file, "r");
            
            short sizeofShort = 2;
            if (rafFile.length() > config.getBlockSize() - sizeofShort) {
                System.out.println(NAME + ": The file size exceeds the size of a block.");
                return null;
            }

            byte[] fileBytes = new byte[(int) rafFile.length()];
            rafFile.read(fileBytes);
            rafFile.close();
            
            return  fileBytes;
        } catch (FileNotFoundException ex) {
            System.out.println(NAME + ": File for recording not found.");
            System.out.println(NAME + ": " + ex.getMessage());
            return null;
        } catch (IOException ex) {
            System.out.println(NAME + ": Error occurred while reading the import file.");
            System.out.println(NAME + ": " + ex.getMessage());
            return null;
        }
    }
    
    public int write(String[] args) {
        
        if (args.length < 3) {
            System.out.println(NAME + ": missing operand after \"" + NAME + "\"");
            System.out.println(NAME + ": use \"" + NAME + " root N file\"");
            return 1;
        }
        
        root = args[0];
        config = new RaidConfig(root);
        
        try {
            config.loadConfig();
        } catch (FileNotFoundException ex) {
            System.out.println(NAME + ": Can not find file with the settings.");
            return 2;
        } catch (IOException ex) {
            System.out.println(NAME + ": Error reading settings file.");
            return 2;
        } catch (NumberFormatException ex) {
            System.out.println(NAME + ": Invalid file format settings.");
            return 2;
        }
        
        if (!config.checkConfig()) return 2;
        
        if (!Utils.checkRaid(root, config)) {
            System.out.println(NAME + ": Some disks are not available or can not be read.");
            System.out.println(NAME + ": Try rebuild raid.");
            return 8;
        }
        
        int block;
        try {
            block = Integer.parseInt(args[1]);
        } catch (NumberFormatException ex) {
            System.out.println(NAME + ": Error parsing number of block.");
            System.out.println(NAME + ": " + ex.getMessage());
            return 3;
        }
        
        String filePath = args[2];
        byte[] bytes = readImportFile(filePath);
        
        if (bytes == null) return 7;
        
        System.out.println(NAME + ": Recording...");
        
        try {
            switch (config.getRaidLevel()) {
                case 0:
                    if (block < 0 || block >= config.getBlocksCount()) {
                        System.out.println(NAME + ": Wrong number of block.");
                        return 4;
                    }
                    
                    writeRaid0(block, bytes);
                break;

                case 1:
                    if (block < 0 || block >= config.getBlocksCount() * config.getDisksCount()) {
                        System.out.println(NAME + ": Wrong number of block.");
                        return 4;
                    }
                    
                    writeRaid1(block, bytes);
                break;
                    
                case 3:
                    if (block < 0 || block >= config.getBlocksCount()) {
                        System.out.println(NAME + ": Wrong number of block.");
                        return 4;
                    }
                    
                    writeRaid3(block, bytes);
                break;
                    
                case 4:
                    if (block < 0 || block >= config.getBlocksCount()) {
                        System.out.println(NAME + ": Wrong number of block.");
                        return 4;
                    }
                    
                    writeRaid4(block, bytes);
                break;
                    
                case 5:
                    if (block < 0 || block >= config.getBlocksCount()) {
                        System.out.println(NAME + ": Wrong number of block.");
                        return 4;
                    }
                    
                    writeRaid5(block, bytes);
                break;
                    
                default: 
                    System.out.println(NAME + ": RAID level is not supported.");
                    return 8;
            }
        } catch (FileNotFoundException ex) {
            System.out.println(NAME + ": Could not find the file.");
            System.out.println(NAME + ": " + ex.getMessage());
            System.out.println(NAME + ": Try rebuild raid.");
            return 5;
        } catch (IOException ex) {
            System.out.println(NAME + ": Error occurred while reading the file.");
            System.out.println(NAME + ": " + ex.getMessage());
            return 6;
        }
        
        System.out.println(NAME + ": Recording is completed successfully.");
        
        return 0;
    }
    
}
